﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024 LiLong                                                   **
**  This file is part of GnssLogs.                                              **
**                                                                              **
**  GnssLogs is free software: you can redistribute it and/or modify            **
**  it under the terms of the GNU Lesser General Public License as published by **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  GnssLogs is distributed in the hope that it will be useful,                 **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU Lesser General Public License for more details.                         **
**                                                                              **
**  You should have received a copy of the GNU Lesser General Public License    **
**  along with GnssLogs.  If not, see <https://www.gnu.org/licenses/>.          **
**********************************************************************************/
#pragma once

#include "LogHeader.h"

#include <variant>

namespace GnssLogs
{
namespace RANGE
{
struct Data
{
    struct ChannelTrackingStatus
    {
        enum class TrackingState : uint32_t
        {
            Idle,
            SkySearch,
            WideFrequencyBandPullIn,
            NarrowFrequencyBandPullIn,
            PhaseLockLoop,
            ChannelSteering,
            FrequencyLockLoop,
            ChannelAlignment,
            CodeSearch,
            AidedPhaseLockLoop,
            SidePeakDetection,
            FFTSkySearch
        };

        enum class CorrelatorType : uint32_t
        {
            NA,
            Standard,
            Narrow,
            Reserved,
            PAC,
            NarrowPAC,
            MEC
        };

        enum class GPSSignalType : uint32_t
        {
            L1C_A = 0,
            L2P   = 5,
            L2P_Y = 9,
            L5    = 14,
            L1C_P = 16,
            L2C_M = 17
        };

        enum class GLONASSSignalType : uint32_t
        {
            L1C_A = 0,
            L2C_A = 1,
            L2P   = 5,
            L3_Q  = 6
        };

        enum class BeiDouSignalType : uint32_t
        {
            B1_D1 = 0,
            B2_D1 = 1,
            B3_D1 = 2,
            B1_D2 = 4,
            B2_D2 = 5,
            B3_D2 = 6,
            B1C_P = 7,
            B2a_P = 9,
            B2b_I = 11
        };

        enum class GalileoSignalType : uint32_t
        {
            E1_C       = 2,
            E6B        = 6,
            E6C        = 7,
            E5a_Q      = 12,
            E5b_Q      = 17,
            E5AltBOC_Q = 20
        };

        enum class QZSSSignalType : uint32_t
        {
            L1C_A = 0,
            L5_Q  = 14,
            L1C_P = 16,
            L2C_M = 17,
            L6P   = 27
        };

        enum class SBASSignalType : uint32_t
        {
            L1C_A = 0,
            L5_I  = 6
        };

        enum class NavICSignalType : uint32_t
        {
            L5SPS = 0
        };

        enum class OtherSignalType : uint32_t
        {
            L_Band = 19
        };

        using SignalType = std::variant<GPSSignalType,
                                        GLONASSSignalType,
                                        SBASSignalType,
                                        GalileoSignalType,
                                        BeiDouSignalType,
                                        QZSSSignalType,
                                        NavICSignalType,
                                        OtherSignalType>;

        TrackingState trackingState       : 5;
        uint32_t svChannelNumber          : 5; // (n-1) (0 = first, n = last)
        uint32_t phaseLockFlag            : 1; // 0 = Not locked, 1 = Locked
        uint32_t parityKnownFlag          : 1; // 0 = Not known, 1 = Known
        uint32_t codeLockedFlag           : 1; // 0 = Not locked, 1 = Locked
        CorrelatorType correlatorType     : 3;
        SatelliteSystem satelliteSystem   : 3;
        uint32_t reserved1                : 1;
        uint32_t grouping                 : 1; // 0 = Not grouped, 1 = Grouped
        uint32_t signalType               : 5; // see SatelliteSystem and fromSignalType
        uint32_t reserved2                : 1;
        uint32_t primaryL1Channel         : 1; // 0 = Not primary, 1 = Primary
        uint32_t carrierPhaseMeasurement  : 1; // 0 = Half Cycle Not Added, 1 = Half Cycle Added
        uint32_t digitalFilteringOnSignal : 1; // 0 = No digital filter, 1 = Digital filter
        uint32_t prnLockFlag              : 1; // 0 = PRN Not Locked Out, 1 = PRN Locked Out
        uint32_t channelAssignment        : 1; // 0 = Automatic, 1 = Forced

        SignalType getSignalType() const
        {
            SignalType type;
            assignSignalType(type, this->signalType, static_cast<int>(satelliteSystem));
            return type;
        }

    private:
        template<int index = 0>
        static void assignSignalType(SignalType& type, uint32_t var, int i)
        {
            if constexpr (index >= std::variant_size_v<SignalType>)
                return; // or throw?
            else
            {
                if (i == index)
                    type = static_cast<std::variant_alternative_t<index, SignalType>>(var);
                else
                    assignSignalType<index + 1>(type, var, i + 1);
            }
        }
    };

    LogHeader::Data header;
    uint32_t obs;
    struct PRN
    {
        uint16_t prnSlot;
        uint16_t glofreq;
        double psr;
        float psrSigma;
        double adr;
        float adrSigma;
        float dopp;
        float cNo;
        float locktime;
        union ChTrStatus
        {
            uint32_t data;
            ChannelTrackingStatus status;
        } chTrStatus;
    };
    std::vector<PRN> prns;
    uint32_t crc32;
};
} // namespace RANGE
} // namespace GnssLogs

GNSSLOGS_DECLARE_FORMATTER(GnssLogs::RANGE::Data::PRN)
GNSSLOGS_DECLARE_FORMATTER(GnssLogs::RANGE::Data)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE,
                                Data::ChannelTrackingStatus::TrackingState,
                                Idle,
                                SkySearch,
                                WideFrequencyBandPullIn,
                                NarrowFrequencyBandPullIn,
                                PhaseLockLoop,
                                ChannelSteering,
                                FrequencyLockLoop,
                                ChannelAlignment,
                                CodeSearch,
                                AidedPhaseLockLoop,
                                SidePeakDetection,
                                FFTSkySearch)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE,
                                Data::ChannelTrackingStatus::CorrelatorType,
                                NA,
                                Standard,
                                Narrow,
                                Reserved,
                                PAC,
                                NarrowPAC,
                                MEC)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE, Data::ChannelTrackingStatus::GPSSignalType, L1C_A, L2P, L2P_Y, L5, L1C_P, L2C_M)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE, Data::ChannelTrackingStatus::GLONASSSignalType, L1C_A, L2C_A, L2P, L3_Q)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE,
                                Data::ChannelTrackingStatus::BeiDouSignalType,
                                B1_D1,
                                B2_D1,
                                B3_D1,
                                B1_D2,
                                B2_D2,
                                B3_D2,
                                B1C_P,
                                B2a_P,
                                B2b_I)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE, Data::ChannelTrackingStatus::GalileoSignalType, E1_C, E6B, E6C, E5a_Q, E5b_Q, E5AltBOC_Q)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE, Data::ChannelTrackingStatus::QZSSSignalType, L1C_A, L5_Q, L1C_P, L2C_M, L6P)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE, Data::ChannelTrackingStatus::SBASSignalType, L1C_A, L5_I)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE, Data::ChannelTrackingStatus::NavICSignalType, L5SPS)
GNSSLOGS_ENUM_DECLARE_FORMATTER(GnssLogs::RANGE, Data::ChannelTrackingStatus::OtherSignalType, L_Band)

template<class CharT>
struct std::formatter<GnssLogs::RANGE::Data::ChannelTrackingStatus, CharT>
{
    constexpr typename std::basic_format_parse_context<CharT>::iterator parse(std::basic_format_parse_context<CharT>& pc)
    {
        std::basic_string<CharT> str(pc.begin(), pc.end());
        if (pc.begin() == pc.end() || *pc.begin() == '}')
        {
            return pc.end();
        }
        else
        {
            throw std::format_error(std::format("Invalid format arg {}", "GnssLogs::RANGE::Data::ChannelTrackingStatus"));
        }
    }

    template<typename OutIterator>
    auto format(const GnssLogs::RANGE::Data::ChannelTrackingStatus& t, std::basic_format_context<OutIterator, CharT>& fc) const
    {
        std::format_to(fc.out(), "{} :\n", "GnssLogs::RANGE::Data::ChannelTrackingStatus");
        std::format_to(fc.out(), "{} : {}\n", "trackingState", boost::describe::enum_to_string(t.trackingState, "unknown"));
        std::format_to(fc.out(), "{} : {}\n", "svChannelNumber", t.svChannelNumber);
        std::format_to(fc.out(), "{} : {}\n", "phaseLockFlag", t.phaseLockFlag);
        std::format_to(fc.out(), "{} : {}\n", "parityKnownFlag", t.parityKnownFlag);
        std::format_to(fc.out(), "{} : {}\n", "codeLockedFlag", t.codeLockedFlag);
        std::format_to(fc.out(), "{} : {}\n", "correlatorType", boost::describe::enum_to_string(t.correlatorType, "unknown"));
        std::format_to(fc.out(), "{} : {}\n", "satelliteSystem", boost::describe::enum_to_string(t.satelliteSystem, "unknown"));
        std::format_to(fc.out(), "{} : {}\n", "reserved1", t.reserved1);
        std::format_to(fc.out(), "{} : {}\n", "grouping", t.grouping);
        std::visit([&](auto e) { std::format_to(fc.out(), "{} : {}\n", "signalType", boost::describe::enum_to_string(e, "unknown")); },
                   t.getSignalType());
        std::format_to(fc.out(), "{} : {}\n", "reserved2", t.reserved2);
        std::format_to(fc.out(), "{} : {}\n", "primaryL1Channel", t.primaryL1Channel);
        std::format_to(fc.out(), "{} : {}\n", "carrierPhaseMeasurement", t.carrierPhaseMeasurement);
        std::format_to(fc.out(), "{} : {}\n", "digitalFilteringOnSignal", t.digitalFilteringOnSignal);
        std::format_to(fc.out(), "{} : {}\n", "prnLockFlag", t.prnLockFlag);
        std::format_to(fc.out(), "{} : {}\n", "channelAssignment", t.channelAssignment);
        return fc.out();
    }
};

template<class CharT>
struct std::formatter<GnssLogs::RANGE::Data::PRN::ChTrStatus, CharT>
{
    constexpr typename std::basic_format_parse_context<CharT>::iterator parse(std::basic_format_parse_context<CharT>& pc)
    {
        std::basic_string<CharT> str(pc.begin(), pc.end());
        if (pc.begin() == pc.end() || *pc.begin() == '}')
        {
            return pc.end();
        }
        else
        {
            throw std::format_error(std::format("Invalid format arg {}", "GnssLogs::RANGE::Data::PRN::ChTrStatus"));
        }
    }

    template<typename OutIterator>
    auto format(const GnssLogs::RANGE::Data::PRN::ChTrStatus& t, std::basic_format_context<OutIterator, CharT>& fc) const
    {
        std::format_to(fc.out(), "{} : \n", t.status);
        return fc.out();
    }
};